//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 1995-2000, The Hypersonic SQL Group.
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Hypersonic SQL Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * on behalf of the Hypersonic SQL Group.
 *
 *
 * For work added by the HSQL Development Group:
 *
 * Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Indexes;
using EffiProz.Core.Errors;
using EffiProz.Core.Persist;


// fredt@users 20020215 - patch 1.7.0 by fredt
// to preserve column size etc. when SELECT INTO TABLE is used
// tony_lai@users 20021020 - patch 1.7.2 - improved aggregates and HAVING
// fredt@users 20021112 - patch 1.7.2 by Nitin Chauhan - use of switch
// rewrite of the majority of multiple if(){}else{} chains with switch(){}
// vorburger@users 20021229 - patch 1.7.2 - null handling
// boucherb@users 200307?? - patch 1.7.2 - resolve param nodes
// boucherb@users 200307?? - patch 1.7.2 - compress constant expr during resolve
// boucherb@users 200307?? - patch 1.7.2 - eager pmd and rsmd
// boucherb@users 20031005 - patch 1.7.2 - optimised LIKE
// boucherb@users 20031005 - patch 1.7.2 - improved IN value lists
// fredt@users 20031012 - patch 1.7.2 - better OUTER JOIN implementation
// thomasm@users 20041001 - patch 1.7.3 - bool undefined handling
// fredt@users 200412xx - patch 1.7.2 - evaluation of time functions
// boucherb@users 20050516 - patch 1.8.0 - remove DITypeInfo usage for faster
//                                         statement compilation


namespace EffiProz.Core
{
    public class Expression
    {

        public const int LEFT = 0;
        public const int RIGHT = 1;
        public const int UNARY = 1;
        public const int BINARY = 2;

        //
        //
        public static Expression[] emptyExpressionArray = new Expression[] { };

        //
        public static Expression EXPR_TRUE = new ExpressionLogical(true);
        public static Expression EXPR_FALSE = new ExpressionLogical(false);

        //
        public static OrderedIntHashSet aggregateFunctionSet =
            new OrderedIntHashSet();

        public static OrderedIntHashSet columnExpressionSet =
            new OrderedIntHashSet();



        public static OrderedIntHashSet subqueryExpressionSet =
            new OrderedIntHashSet();


        public static OrderedIntHashSet subqueryAggregateExpressionSet =
            new OrderedIntHashSet();

        public static OrderedIntHashSet emptyExpressionSet =
             new OrderedIntHashSet();

        static Expression()
        {
            aggregateFunctionSet.add(OpTypes.COUNT);
            aggregateFunctionSet.add(OpTypes.SUM);
            aggregateFunctionSet.add(OpTypes.MIN);
            aggregateFunctionSet.add(OpTypes.MAX);
            aggregateFunctionSet.add(OpTypes.AVG);
            aggregateFunctionSet.add(OpTypes.EVERY);
            aggregateFunctionSet.add(OpTypes.SOME);
            aggregateFunctionSet.add(OpTypes.STDDEV_POP);
            aggregateFunctionSet.add(OpTypes.STDDEV_SAMP);
            aggregateFunctionSet.add(OpTypes.VAR_POP);
            aggregateFunctionSet.add(OpTypes.VAR_SAMP);

            columnExpressionSet.add(OpTypes.COLUMN);

            subqueryExpressionSet.add(OpTypes.ROW_SUBQUERY);
            subqueryExpressionSet.add(OpTypes.TABLE_SUBQUERY);

            subqueryAggregateExpressionSet.add(OpTypes.COUNT);
            subqueryAggregateExpressionSet.add(OpTypes.SUM);
            subqueryAggregateExpressionSet.add(OpTypes.MIN);
            subqueryAggregateExpressionSet.add(OpTypes.MAX);
            subqueryAggregateExpressionSet.add(OpTypes.AVG);
            subqueryAggregateExpressionSet.add(OpTypes.EVERY);
            subqueryAggregateExpressionSet.add(OpTypes.SOME);
            subqueryAggregateExpressionSet.add(OpTypes.STDDEV_POP);
            subqueryAggregateExpressionSet.add(OpTypes.STDDEV_SAMP);
            subqueryAggregateExpressionSet.add(OpTypes.VAR_POP);
            subqueryAggregateExpressionSet.add(OpTypes.VAR_SAMP);

            //
            subqueryAggregateExpressionSet.add(OpTypes.TABLE_SUBQUERY);
            subqueryAggregateExpressionSet.add(OpTypes.ROW_SUBQUERY);
        }



        // type
        public int opType;

        // type qualifier
        public int exprSubType;

        //
        public QNameManager.SimpleName alias;

        // aggregate
        public bool isAggregate;

        // VALUE
        public Object valueData;
        public Expression[] nodes;
        public SqlType[] nodeDataTypes;

        // QUERY - in single value selects, IN, EXISTS etc.
        public SubQuery subQuery;

        // for query and value lists, etc
        public bool _isCorrelated;

        // for COLUMN
        public int columnIndex = -1;

        // data type
        public SqlType dataType;

        protected byte _nullability = SchemaObjectNullability.NULLABLE;

        //
        public int queryTableColumnIndex = -1;    // >= 0 when it is used for order by
        public bool _isParam;

        // index of a session-dependent field
        public int parameterIndex = -1;

        //
        public int rangePosition = -1;

        //
        public bool isColumnEqual;

        public Expression(int type)
        {
            opType = type;
            nodes = emptyExpressionArray;
        }

        // IN condition optimisation

        /**
         * Creates a SCALAR SUBQUERY expression. Is called as ROW_SUBQUERY
         */
        public Expression(int exprType, SubQuery sq)
            : this(OpTypes.TABLE_SUBQUERY)
        {

            subQuery = sq;
        }

        /**
         * ROW or VALUELIST
         */
        public Expression(int type, Expression[] list)
            : this(type)
        {

            this.nodes = list;
        }

        public virtual String describe(Session session)
        {
            return describe(session, 0);
        }

        public static String getContextSQL(Expression expression)
        {

            if (expression == null)
            {
                return null;
            }

            String ddl = expression.getSQL();

            switch (expression.opType)
            {

                case OpTypes.VALUE:
                case OpTypes.COLUMN:
                case OpTypes.ROW:
                case OpTypes.FUNCTION:
                case OpTypes.SQL_FUNCTION:
                case OpTypes.ALTERNATIVE:
                case OpTypes.CASEWHEN:
                case OpTypes.CAST:
                    return ddl;
            }

            StringBuilder sb = new StringBuilder();

            ddl = sb.Append('(').Append(ddl).Append(')').ToString();

            return ddl;
        }

        /**
         * For use with CHECK constraints. Under development.
         *
         * Currently supports a subset of expressions and is suitable for CHECK
         * search conditions that refer only to the inserted/updated row.
         *
         * For full DDL reporting of VIEW select queries and CHECK search
         * conditions, future improvements here are dependent upon improvements to
         * SELECT query parsing, so that it is performed in a number of passes.
         * An early pass should result in the query turned into an Expression tree
         * that contains the information in the original SQL without any
         * alterations, and with tables and columns all resolved. This Expression
         * can then be preserved for future use. Table and column names that
         * are not user-defined aliases should be kept as the QNameManager.QName structures
         * so that table or column renaming is reflected in the precompiled
         * query.
         */
        public virtual String getSQL()
        {

            StringBuilder sb = new StringBuilder(64);

            switch (opType)
            {

                case OpTypes.VALUE:
                    if (valueData == null)
                    {
                        return Tokens.T_NULL;
                    }

                    return dataType.convertToSQLString(valueData);

                case OpTypes.ROW:
                    sb.Append('(');

                    for (int i = 0; i < nodes.Length; i++)
                    {
                        sb.Append(nodes[i].getSQL());

                        if (i < nodes.Length - 1)
                        {
                            sb.Append(',');
                        }
                    }

                    sb.Append(')');

                    return sb.ToString();

                //
                case OpTypes.TABLE:
                    for (int i = 0; i < nodes.Length; i++)
                    {
                        sb.Append(nodes[i].getSQL());

                        if (i < nodes.Length - 1)
                        {
                            sb.Append(',');
                        }
                    }

                    return sb.ToString();
            }

            switch (opType)
            {

                case OpTypes.ROW_SUBQUERY:
                case OpTypes.TABLE_SUBQUERY:
                    /*
                                    buf.Append('(');
                                    buf.Append(subSelect.getSQL());
                                    buf.Append(')');
                    */
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
            }

            return sb.ToString();
        }

        public virtual String describe(Session session, int blanks)
        {

            StringBuilder sb = new StringBuilder(64);

            sb.Append('\n');

            for (int i = 0; i < blanks; i++)
            {
                sb.Append(' ');
            }

            switch (opType)
            {

                case OpTypes.VALUE:
                    sb.Append("VALUE = ").Append(valueData);
                    sb.Append(", TYPE = ").Append(dataType.getNameString());

                    return sb.ToString();

                case OpTypes.ROW_SUBQUERY:
                case OpTypes.TABLE_SUBQUERY:
                    sb.Append("QUERY ");
                    sb.Append(subQuery.queryExpression.describe(session));

                    return sb.ToString();

                case OpTypes.ROW:
                    sb.Append("ROW = ");

                    for (int i = 0; i < nodes.Length; i++)
                    {
                        sb.Append(nodes[i].describe(session, blanks + 1));
                    }

                    sb.Append("), TYPE = ").Append(dataType.getNameString());
                    break;

                case OpTypes.TABLE:
                    sb.Append("VALUELIST ");

                    for (int i = 0; i < nodes.Length; i++)
                    {
                        sb.Append(nodes[i].describe(session, blanks + 1));
                        sb.Append(' ');
                    }
                    break;
            }

            if (nodes[LEFT] != null)
            {
                sb.Append(" arg_left=[");
                sb.Append(nodes[LEFT].describe(session, blanks + 1));
                sb.Append(']');
            }

            if (nodes[RIGHT] != null)
            {
                sb.Append(" arg_right=[");
                sb.Append(nodes[RIGHT].describe(session, blanks + 1));
                sb.Append(']');
            }

            return sb.ToString();
        }

        /**
         * Set the data type
         */
        public void setDataType(Session session, SqlType type)
        {

            if (opType == OpTypes.VALUE)
            {
                valueData = type.convertToType(session, valueData, dataType);
            }

            dataType = type;
        }



        public virtual bool Equals(Expression other)
        {

            if (other == this)
            {
                return true;
            }

            if (other == null)
            {
                return false;
            }

            if (opType != other.opType || exprSubType != other.exprSubType
                    || !Equals(dataType, other.dataType))
            {
                return false;
            }

            switch (opType)
            {

                case OpTypes.SIMPLE_COLUMN:
                    return this.columnIndex == other.columnIndex;

                case OpTypes.VALUE:
                    return Equals(valueData, other.valueData);

                default:
                    return Equals(nodes, other.nodes)
                           && Equals(subQuery, other.subQuery);
            }
        }

        public override bool Equals(Object other)
        {

            if (other == this)
            {
                return true;
            }

            if (other is Expression)
            {
                return Equals((Expression)other);
            }

            return false;
        }

        public override int GetHashCode()
        {

            int val = opType + exprSubType;

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] != null)
                {
                    val += nodes[i].GetHashCode();
                }
            }

            return val;
        }

        public static new  bool Equals(Object o1, Object o2)
        {

            if (o1 == o2)
            {
                return true;
            }

            return (o1 == null) ? o2 == null
                                : o1.Equals(o2);
        }

        static bool Equals(Expression[] row1, Expression[] row2)
        {

            if (row1 == row2)
            {
                return true;
            }

            if (row1.Length != row2.Length)
            {
                return false;
            }

            int len = row1.Length;

            for (int i = 0; i < len; i++)
            {
                Expression e1 = row1[i];
                Expression e2 = row2[i];
                bool equals = (e1 == null) ? e2 == null
                                                 : e1.Equals(e2);

                if (!equals)
                {
                    return false;
                }
            }

            return true;
        }

        public bool isComposedOf(Expression[] exprList, int start, int end,
                             OrderedIntHashSet excludeSet)
        {

            if (opType == OpTypes.VALUE)
            {
                return true;
            }

            if (excludeSet.contains(opType))
            {
                return true;
            }

            for (int i = start; i < end; i++)
            {
                if (Equals(exprList[i]))
                {
                    return true;
                }
            }

            switch (opType)
            {

                case OpTypes.LIKE:
                case OpTypes.MATCH_SIMPLE:
                case OpTypes.MATCH_PARTIAL:
                case OpTypes.MATCH_FULL:
                case OpTypes.MATCH_UNIQUE_SIMPLE:
                case OpTypes.MATCH_UNIQUE_PARTIAL:
                case OpTypes.MATCH_UNIQUE_FULL:
                case OpTypes.UNIQUE:
                case OpTypes.EXISTS:
                case OpTypes.TABLE_SUBQUERY:
                case OpTypes.ROW_SUBQUERY:

                //
                case OpTypes.COUNT:
                case OpTypes.SUM:
                case OpTypes.MIN:
                case OpTypes.MAX:
                case OpTypes.AVG:
                case OpTypes.EVERY:
                case OpTypes.SOME:
                case OpTypes.STDDEV_POP:
                case OpTypes.STDDEV_SAMP:
                case OpTypes.VAR_POP:
                case OpTypes.VAR_SAMP:
                    return false;
            }

            if (nodes.Length == 0)
            {
                return false;
            }

            bool result = true;

            for (int i = 0; i < nodes.Length; i++)
            {
                result &= (nodes[i] == null
                           || nodes[i].isComposedOf(exprList, start, end,
                                                    excludeSet));
            }

            return result;
        }

        public bool isComposedOf(OrderedHashSet expressions,
                             OrderedIntHashSet excludeSet)
        {

            if (opType == OpTypes.VALUE)
            {
                return true;
            }

            if (excludeSet.contains(opType))
            {
                return true;
            }

            for (int i = 0; i < expressions.size(); i++)
            {
                if (Equals(expressions.get(i)))
                {
                    return true;
                }
            }

            switch (opType)
            {

                case OpTypes.COUNT:
                case OpTypes.SUM:
                case OpTypes.MIN:
                case OpTypes.MAX:
                case OpTypes.AVG:
                case OpTypes.EVERY:
                case OpTypes.SOME:
                case OpTypes.STDDEV_POP:
                case OpTypes.STDDEV_SAMP:
                case OpTypes.VAR_POP:
                case OpTypes.VAR_SAMP:
                    return false;
            }

            /*
                    case OpCodes.LIKE :
                    case OpCodes.ALL :
                    case OpCodes.ANY :
                    case OpCodes.IN :
                    case OpCodes.MATCH_SIMPLE :
                    case OpCodes.MATCH_PARTIAL :
                    case OpCodes.MATCH_FULL :
                    case OpCodes.MATCH_UNIQUE_SIMPLE :
                    case OpCodes.MATCH_UNIQUE_PARTIAL :
                    case OpCodes.MATCH_UNIQUE_FULL :
                    case OpCodes.UNIQUE :
                    case OpCodes.EXISTS :
                    case OpCodes.TABLE_SUBQUERY :
                    case OpCodes.ROW_SUBQUERY :
            */
            if (nodes.Length == 0)
            {
                return false;
            }

            bool result = true;

            for (int i = 0; i < nodes.Length; i++)
            {
                result &= (nodes[i] == null
                           || nodes[i].isComposedOf(expressions, excludeSet));
            }

            return result;
        }

        Expression replace(OrderedHashSet expressions,
                           OrderedHashSet replacements)
        {

            if (opType == OpTypes.VALUE)
            {
                return this;
            }

            int index = expressions.getIndex(this);

            if (index != -1)
            {
                return (Expression)replacements.get(index);
            }

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] == null)
                {
                    continue;
                }

                nodes[i] = nodes[i].replace(expressions, replacements);
            }

            return this;
        }

        public virtual Expression replaceColumnReferences(RangeVariable range,
                                           Expression[] list)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] == null)
                {
                    continue;
                }

                nodes[i] = nodes[i].replaceColumnReferences(range, list);
            }

            return this;
        }

        public void convertToSimpleColumn(OrderedHashSet expressions,
                                   OrderedHashSet replacements)
        {

            if (opType == OpTypes.VALUE)
            {
                return;
            }

            int index = expressions.getIndex(this);

            if (index != -1)
            {
                Expression e = (Expression)replacements.get(index);

                nodes = emptyExpressionArray;
                opType = OpTypes.SIMPLE_COLUMN;
                columnIndex = e.columnIndex;
                rangePosition = e.rangePosition;

                return;
            }

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] == null)
                {
                    continue;
                }

                nodes[i].convertToSimpleColumn(expressions, replacements);
            }
        }

        public virtual bool isSelfAggregate()
        {
            return false;
        }

        /**
         * Set the column alias
         */
        public virtual void setAlias(QNameManager.SimpleName name)
        {
            alias = name;
        }

        /**
         * Get the column alias
         */
        public virtual String getAlias()
        {

            if (alias != null)
            {
                return alias.name;
            }

            return "";
        }

        public virtual String getOriginalAlias()
        {

            if (alias != null)
            {
                return alias.originalName;
            }

            return "";
        }

       public virtual QNameManager.SimpleName getSimpleName()
        {

            if (alias != null)
            {
                return alias;
            }

            return null;
        }

        /**
         * Returns the type of expression
         */
        public int getType()
        {
            return opType;
        }

        /**
         * Returns the left node
         */
        public Expression getLeftNode()
        {
            return nodes.Length > 0 ? nodes[LEFT]
                                    : null;
        }

        /**
         * Returns the right node
         */
        public Expression getRightNode()
        {
            return nodes.Length > 1 ? nodes[RIGHT]
                                    : null;
        }

        public void setLeftNode(Expression e)
        {
            nodes[LEFT] = e;
        }

        public void setRightNode(Expression e)
        {
            nodes[RIGHT] = e;
        }

        /**
         * Returns the range variable for a COLUMN expression
         */
        public virtual RangeVariable getRangeVariable()
        {
            return null;
        }

        /**
         * return the expression for an alias used in an ORDER BY clause
         */
        public virtual Expression replaceAliasInOrderBy(Expression[] columns, int length)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] == null)
                {
                    continue;
                }

                nodes[i] = nodes[i].replaceAliasInOrderBy(columns, length);
            }

            return this;
        }

        /**
         * Find a range variable with the given table alias
         */
        public virtual int findMatchingRangeVariableIndex(RangeVariable[] rangeVarArray)
        {
            return -1;
        }

        /**
         * collects all range variables in expression tree
         */
       public virtual void collectRangeVariables(RangeVariable[] rangeVariables, Set set)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] != null)
                {
                    nodes[i].collectRangeVariables(rangeVariables, set);
                }
            }

            if (subQuery != null && subQuery.queryExpression != null)
            {
                EfzList unresolvedExpressions =
                    subQuery.queryExpression.getUnresolvedExpressions();

                if (unresolvedExpressions != null)
                {
                    for (int i = 0; i < unresolvedExpressions.size(); i++)
                    {
                        Expression e = (Expression)unresolvedExpressions.get(i);

                        e.collectRangeVariables(rangeVariables, set);
                    }
                }
            }
        }

        /**
         * collects all range variables in expression tree
         */
       public virtual void collectObjectNames(Set set)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] != null)
                {
                    nodes[i].collectObjectNames(set);
                }
            }

            if (subQuery != null)
            {
                if (subQuery.queryExpression != null)
                {
                    subQuery.queryExpression.collectObjectNames(set);
                }
            }
        }

        /**
         * return true if given RangeVariable is used in expression tree
         */
        public virtual bool hasReference(RangeVariable range)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] != null)
                {
                    if (nodes[i].hasReference(range))
                    {
                        return true;
                    }
                }
            }

            if (subQuery != null && subQuery.queryExpression != null)
            {
                if (subQuery.queryExpression.hasReference(range))
                {
                    return true;
                }
            }

            return false;
        }

        /**
         * resolve tables and collect unresolved column expressions
         */
        public virtual EfzList resolveColumnReferences(RangeVariable[] rangeVarArray,
                EfzList unresolvedSet)
        {
            return resolveColumnReferences(rangeVarArray, rangeVarArray.Length,
                                           unresolvedSet, true);
        }

        public virtual EfzList resolveColumnReferences(RangeVariable[] rangeVarArray,
                int rangeCount, EfzList unresolvedSet, bool acceptsSequences)
        {

            if (opType == OpTypes.VALUE)
            {
                return unresolvedSet;
            }

            switch (opType)
            {

                case OpTypes.CASEWHEN:
                    acceptsSequences = false;
                    break;

                case OpTypes.TABLE:
                    {
                        EfzList localSet = null;

                        for (int i = 0; i < nodes.Length; i++)
                        {
                            if (nodes[i] == null)
                            {
                                continue;
                            }

                            localSet = nodes[i].resolveColumnReferences(
                                RangeVariable.emptyArray, localSet);
                        }

                        if (localSet != null)
                        {
                            _isCorrelated = true;

                            if (subQuery != null)
                            {
                                subQuery.setCorrelated();
                            }

                            for (int i = 0; i < localSet.size(); i++)
                            {
                                Expression e = (Expression)localSet.get(i);

                                unresolvedSet =
                                    e.resolveColumnReferences(rangeVarArray,
                                                              unresolvedSet);
                            }

                            unresolvedSet = Expression.resolveColumnSet(rangeVarArray,
                                    localSet, unresolvedSet);
                        }

                        return unresolvedSet;
                    }
            }

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] == null)
                {
                    continue;
                }

                unresolvedSet = nodes[i].resolveColumnReferences(rangeVarArray,
                        rangeCount, unresolvedSet, acceptsSequences);
            }

            switch (opType)
            {

                case OpTypes.ROW_SUBQUERY:
                case OpTypes.TABLE_SUBQUERY:
                    {
                        QueryExpression queryExpression = subQuery.queryExpression;

                        if (!queryExpression.areColumnsResolved())
                        {
                            _isCorrelated = true;

                            subQuery.setCorrelated();

                            // take to enclosing context
                            if (unresolvedSet == null)
                            {
                                unresolvedSet = new ArrayListIdentity();
                            }

                            unresolvedSet.addAll(
                                queryExpression.getUnresolvedExpressions());
                        }

                        break;
                    }
                default:
                    break;
            }

            return unresolvedSet;
        }

        public virtual OrderedHashSet getUnkeyedColumns(OrderedHashSet unresolvedSet)
        {

            if (opType == OpTypes.VALUE)
            {
                return unresolvedSet;
            }

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] == null)
                {
                    continue;
                }

                unresolvedSet = nodes[i].getUnkeyedColumns(unresolvedSet);
            }

            switch (opType)
            {

                case OpTypes.ROW_SUBQUERY:
                case OpTypes.TABLE_SUBQUERY:
                    if (subQuery != null)
                    {
                        if (unresolvedSet == null)
                        {
                            unresolvedSet = new OrderedHashSet();
                        }

                        unresolvedSet.add(this);
                    }
                    break;
            }

            return unresolvedSet;
        }

        public virtual void resolveTypes(Session session, Expression parent)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] != null)
                {
                    nodes[i].resolveTypes(session, this);
                }
            }

            switch (opType)
            {

                case OpTypes.VALUE:
                    break;

                case OpTypes.TABLE:

                    /** @todo - should it fall through */
                    break;

                case OpTypes.ROW:
                    nodeDataTypes = new SqlType[nodes.Length];

                    for (int i = 0; i < nodes.Length; i++)
                    {
                        if (nodes[i] != null)
                        {
                            nodeDataTypes[i] = nodes[i].dataType;
                        }
                    }
                    break;

                case OpTypes.ROW_SUBQUERY:
                case OpTypes.TABLE_SUBQUERY:
                    {
                        QueryExpression queryExpression = subQuery.queryExpression;

                        queryExpression.resolveTypes(session);
                        subQuery.prepareTable(session);

                        nodeDataTypes = queryExpression.getColumnTypes();
                        dataType = nodeDataTypes[0];

                        break;
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
            }
        }

        public void setAsConstantValue(Session session)
        {

            valueData = getConstantValue(session);
            opType = OpTypes.VALUE;
            nodes = emptyExpressionArray;
        }

        public void setAsConstantValue(Object value)
        {

            valueData = value;
            opType = OpTypes.VALUE;
            nodes = emptyExpressionArray;
        }

        public void prepareTable(Session session, Expression row, int degree)
        {

            if (nodeDataTypes != null)
            {
                return;
            }

            for (int i = 0; i < nodes.Length; i++)
            {
                Expression e = nodes[i];

                if (e.opType == OpTypes.ROW)
                {
                    if (degree != e.nodes.Length)
                    {
                        throw Error.error(ErrorCode.X_42564);
                    }
                }
                else if (degree == 1)
                {
                    nodes[i] = new Expression(OpTypes.ROW);
                    nodes[i].nodes = new Expression[] { e };
                }
                else
                {
                    throw Error.error(ErrorCode.X_42564);
                }
            }

            nodeDataTypes = new SqlType[degree];

            for (int j = 0; j < degree; j++)
            {
                SqlType type = row == null ? null
                                        : row.nodes[j].dataType;

                for (int i = 0; i < nodes.Length; i++)
                {
                    type = SqlType.getAggregateType(nodes[i].nodes[j].dataType, type);
                }

                if (type == null)
                {
                    throw Error.error(ErrorCode.X_42567);
                }

                nodeDataTypes[j] = type;

                if (row != null && row.nodes[j]._isParam)
                {
                    row.nodes[j].dataType = type;
                }

                for (int i = 0; i < nodes.Length; i++)
                {
                    if (nodes[i].nodes[j]._isParam)
                    {
                        nodes[i].nodes[j].dataType = nodeDataTypes[j];

                        continue;
                    }

                    if (nodes[i].nodes[j].opType == OpTypes.VALUE)
                    {
                        if (nodes[i].nodes[j].valueData == null)
                        {
                            nodes[i].nodes[j].dataType = nodeDataTypes[j];
                        }
                    }
                }

                if (nodeDataTypes[j].isCharacterType()
                        && !((CharacterType)nodeDataTypes[j])
                            .isEqualIdentical())
                {

                    // collation issues
                }
            }
        }

        /**
         * Details of IN condition optimisation for 1.9.0
         * Predicates with SELECT are QUERY expressions
         *
         * Predicates with IN list
         *
         * Parser adds a SubQuery to the list for each predicate
         * At type resolution IN lists that are entirely fixed constant or parameter
         * values are selected for possible optimisation. The flags:
         *
         * IN expression right side isCorrelated == true if there are non-constant,
         * non-param expressions in the list (Expressions may have to be resolved
         * against the full set of columns of the query, so must be re-evaluated
         * for each row and evaluated after all the joins have been made)
         *
         * VALUELIST expression isFixedConstantValueList == true when all
         * expressions are fixed constant and none is a param. With this flag,
         * a single-column VALUELIST can be accessed as a HashMap.
         *
         * Predicates may be optimised as joins if isCorrelated == false
         *
         */
        public void insertValuesIntoSubqueryTable(Session session,
                                           PersistentStore store)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                Object[] data = nodes[i].getRowValue(session);

                for (int j = 0; j < nodeDataTypes.Length; j++)
                {
                    data[j] = nodeDataTypes[j].convertToType(session, data[j],
                            nodes[i].nodes[j].dataType);
                }

                Row row = (Row)store.getNewCachedObject(session, data);

                try
                {
                    store.indexRow(session, row);
                }
                catch (CoreException ) { }
            }
        }

        /**
         * Returns the name of a column as string
         *
         * @return column name
         */
        public virtual String getColumnName()
        {
            return getAlias();
        }

        public virtual ColumnSchema getColumn()
        {
            return null;
        }

        public virtual byte getNullability()
        {
            return _nullability;
        }

        public void setNullability(byte nullability)
        {
            _nullability = nullability;
        }

        /**
         * Returns the column index in the table
         */
        public int getColumnIndex()
        {
            return columnIndex;
        }

        /**
         * Returns the data type
         */
        public SqlType getDataType()
        {
            return dataType;
        }

        public int getDegree()
        {
            return opType == OpTypes.ROW ? nodes.Length
                                         : 1;
        }

        public Object[] getRowValue(Session session)
        {

            switch (opType)
            {

                case OpTypes.ROW:
                    {
                        Object[] data = new Object[nodes.Length];

                        for (int i = 0; i < nodes.Length; i++)
                        {
                            data[i] = nodes[i].getValue(session);
                        }

                        return data;
                    }
                case OpTypes.ROW_SUBQUERY:
                case OpTypes.TABLE_SUBQUERY:
                    {
                        return subQuery.queryExpression.getValues(session);
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
            }
        }

       public Object getValue(Session session, SqlType type)
        {

            Object o = getValue(session);

            if (o == null || dataType == type)
            {
                return o;
            }

            return type.convertToType(session, o, dataType);
        }

        public Object getConstantValue(Session session)
        {
            return getValue(session);
        }

        public Object getConstantValueNoCheck(Session session)
        {

            try
            {
                return getValue(session);
            }
            catch (CoreException )
            {
                return null;
            }
        }

        public virtual Object getValue(Session session)
        {

            switch (opType)
            {

                case OpTypes.VALUE:
                    return valueData;

                case OpTypes.SIMPLE_COLUMN:
                    {
                        Object[] data =
                            session.sessionContext.rangeIterators[rangePosition]
                                .getCurrent();

                        return data[columnIndex];
                    }
                case OpTypes.ROW:
                    {
                        if (nodes.Length == 1)
                        {
                            return nodes[0].getValue(session);
                        }

                        Object[] row = new Object[nodes.Length];

                        for (int i = 0; i < nodes.Length; i++)
                        {
                            row[i] = nodes[i].getValue(session);
                        }

                        return row;
                    }
                case OpTypes.ROW_SUBQUERY:
                case OpTypes.TABLE_SUBQUERY:
                    {
                        subQuery.materialiseCorrelated(session);

                        Object value = subQuery.getValue(session);

                        return value;
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
            }
        }

        public bool testCondition(Session session)
        {
            return true.Equals(getValue(session));
        }

        public static int countNulls(Object[] a)
        {

            int nulls = 0;

            for (int i = 0; i < a.Length; i++)
            {
                if (a[i] == null)
                {
                    nulls++;
                }
            }

            return nulls;
        }

        public static void convertToType(Session session, Object[] data, SqlType[] dataType,
                                  SqlType[] newType)
        {

            for (int i = 0; i < data.Length; i++)
            {
                data[i] = newType[i].convertToType(session, data[i], dataType[i]);
            }
        }

        /**
         * Returns a Select object that can be used for checking the contents
         * of an existing table against the given CHECK search condition.
         */
        public static QuerySpecification getCheckSelect(Session session, Table t,
                Expression e)
        {

            ParserDML.CompileContext compileContext = new ParserDML.CompileContext(session);
            QuerySpecification s = new QuerySpecification(compileContext);

            s.exprColumns = new Expression[1];
            s.exprColumns[0] = new ExpressionLogical(true);

            RangeVariable range = new RangeVariable(t, null, null, null,
                compileContext);

            s.rangeVariables = new RangeVariable[] { range };

            EfzList unresolved = e.resolveColumnReferences(s.rangeVariables,
                null);

            ExpressionColumn.checkColumnsResolved(unresolved);
            e.resolveTypes(session, null);

            if (SqlType.SQL_BOOLEAN != e.getDataType())
            {
                throw Error.error(ErrorCode.X_42568);
            }

            Expression condition = new ExpressionLogical(OpTypes.NOT, e);

            s.queryCondition = condition;

            s.resolveReferences(session);
            s.resolveTypes(session);

            return s;
        }

        public static void resolveGenerationExpression(Session session, Table t,
                Expression e)
        {

            EfzList unresolved = e.resolveColumnReferences(t.defaultRanges, null);

            ExpressionColumn.checkColumnsResolved(unresolved);
            e.resolveTypes(session, null);

            OrderedHashSet set = new OrderedHashSet();

            e.collectObjectNames(set);

            for (int i = 0; i < set.size(); i++)
            {
                QNameManager.QName name = (QNameManager.QName)set.get(i);

                if (name.type == SchemaObjectTypes.COLUMN)
                {
                    int colIndex = t.findColumn(name.name);
                    ColumnSchema column = t.getColumn(colIndex);

                    if (column.isGenerated())
                    {
                        throw Error.error(ErrorCode.X_42512);
                    }

                    /** todo - deterministic, non-sql function */
                }
            }
        }

        public bool isParam()
        {
            return _isParam;
        }

        public virtual void setAttributesAsColumn(ColumnSchema column, bool isWritable)
        {
            throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
        }

        String getValueClassName()
        {

            SqlType type = dataType == null ? NullType.getNullType()
                                         : dataType;

            return type.getCSharpClassName();
        }

        public void collectAllFunctionExpressions(EfzList set)
        {

            Expression.collectAllExpressions(set, this,
                                             Expression.emptyExpressionSet,
                                             Expression.emptyExpressionSet);
        }

        /**
         * collect all extrassions of a set of expression types appearing anywhere
         * in a select statement and its subselects, etc.
         */
        public static void collectAllExpressions(EfzList set, Expression e,
                                          OrderedIntHashSet typeSet,
                                          OrderedIntHashSet stopAtTypeSet)
        {

            if (e == null)
            {
                return;
            }

            if (stopAtTypeSet.contains(e.opType))
            {
                return;
            }

            for (int i = 0; i < e.nodes.Length; i++)
            {
                collectAllExpressions(set, e.nodes[i], typeSet, stopAtTypeSet);
            }

            if (typeSet.contains(e.opType))
            {
                set.add(e);
            }

            if (e.subQuery != null && e.subQuery.queryExpression != null)
            {
                e.subQuery.queryExpression.collectAllExpressions(set, typeSet,
                        stopAtTypeSet);
            }
        }

        /**
         * isCorrelated
         */
        public bool isCorrelated()
        {

            if (opType == OpTypes.TABLE_SUBQUERY && subQuery != null
                    && subQuery.isCorrelated())
            {
                return true;
            }

            return false;
        }

        /**
         * checkValidCheckConstraint
         */
        public void checkValidCheckConstraint()
        {

            EfzArrayList set = new EfzArrayList();

            Expression.collectAllExpressions(set, this, subqueryExpressionSet,
                                             emptyExpressionSet);

            if (!set.isEmpty())
            {
                throw Error.error(ErrorCode.X_0A000,
                                  "subquery in check constraint");
            }
        }

        public static EfzList resolveColumnSet(RangeVariable[] rangeVars,
                                         EfzList sourceSet, EfzList targetSet)
        {

            if (sourceSet == null)
            {
                return targetSet;
            }

            for (int i = 0; i < sourceSet.size(); i++)
            {
                Expression e = (Expression)sourceSet.get(i);

                targetSet = e.resolveColumnReferences(rangeVars, targetSet);
            }

            return targetSet;
        }

        public virtual Expression getIndexableExpression(RangeVariable rangeVar)
        {
            return null;
        }
    }

}
